"""
Dependencies:
pyPortMidi

Works with TouchOSC from Hexler
"""

import pypm
import OSC

debug = True

pypm.Initialize() # always call this first, or OS may crash when you try to open a stream
import socket 
#s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 
#routerIPaddress = '192.168.3.1'
#s.connect((routerIPaddress, 0)) 
IPaddress = '169.254.143.198' #s.getsockname()[0] #own ip address
remoteIPaddress = '169.254.228.161'
print "Local IP address is: " + IPaddress
print "Remote IP addres is: " + remoteIPaddress
incomingPort = 8000
outgoingPort = 9000
import os
targetOutMidiPortName = 'MIDI Yoke NT:  2'
targetInMidiPortName = 'MIDI Yoke NT:  3'
if os.uname()[0] == 'Darwin':
	targerOutMidiPortName = 'IAC Driver Bus 3'
	targerInMidiPortName = 'IAC Driver Bus 2'

midiPortName = ""
midiOutPortNum = 0
while 1:
	sysName, midiPortName, isInput, isOutput, a = pypm.GetDeviceInfo(midiOutPortNum)
	if ((midiPortName == targerOutMidiPortName) & isOutput):
		break
	midiOutPortNum += 1

latency = 1
midiOutput = pypm.Output(midiOutPortNum, latency)

midiInPortNum = 0
while 1:
	sysName, midiPortName, isInput, isOutput, a = pypm.GetDeviceInfo(midiInPortNum)
	if ((midiPortName == targerInMidiPortName) & isInput):
		break
	midiInPortNum += 1
midiInput = pypm.Input(midiInPortNum)

import beatmachine
bMachine = beatmachine.beatmachine()


def myTest():
	OSC.init()
	
##	OSC.createListener() # this defaults to port 9001 as well
	OSC.listen(IPaddress, incomingPort)

	# bind addresses to functions -> oscMessage() function will be triggered everytime a
	# message arrives with a label that has not been previously bound
	OSC.bind(oscMessage, "default")

	import time # in this example we will have a small delay in the while loop

	print 'ready to receive and send osc messages ...'
	
	try:
		debugMidiIn = False

		while 1:
			while not midiInput.Poll(): pass
			midiData = midiInput.Read(1)
			if debugMidiIn: print "Got message : time ",midiData[0][1]
			if debugMidiIn: print  midiData[0][0][0]," ",midiData[0][0][1]," ",midiData[0][0][2], midiData[0][0][3]
			OSC.sendMsg("/1/fader1", [0.5], remoteIPaddress, outgoingPort) # !! it sends by default to localhost ip "127.0.0.1" and port 9000 
			notes = []
			
			if int(midiData[0][0][0]) == 144:
				ledNum = int(midiData[0][0][1]) - 63
				if debugMidiIn: print "led ", ledNum
				if (ledNum > 0) & (ledNum < 17):
					if debugMidiIn: print "blink"
					OSC.sendMsg("/2/led" + str(ledNum), [1], remoteIPaddress, outgoingPort) # !! it sends by default to localhost ip "127.0.0.1" and port 
					notes = bMachine.activateColumn(ledNum - 1, "NoteOn")
			if int(midiData[0][0][0]) == 128:
				ledNum = int(midiData[0][0][1]) - 63
				if debugMidiIn: print "led ", ledNum
				if (ledNum > 0) & (ledNum < 17):
					if debugMidiIn: print "off"
					OSC.sendMsg("/2/led" + str(ledNum), [0], remoteIPaddress, outgoingPort) # !! it sends by default to localhost ip "127.0.0.1" and 
					notes = bMachine.activateColumn(ledNum - 1, "NoteOff")
			for n in notes:
			   type, channel, d1, d2 = n
			   midiMessage(type, channel, d1, d2)
			
			#OSC.getOSC(inSocket) # listen to incomming OSC in this socket
			time.sleep(0.001) # you don't need this, but otherwise we're sending as fast as possible.
	except KeyboardInterrupt:
		OSC.dontListen() # finally close the connection bfore exiting or program
		raise SystemExit
	OSC.dontListen() # finally close the connection bfore exiting or program


""" Below are some functions dealing with OSC messages RECEIVED to Python.

	Here you can set all the responders you need to deal with the incoming
	OSC messages. You need them to the callBackManager instance in the main
	loop and associate them to the desired OSC addreses like this for example
	addressManager.add(printStuff, "/print")
	it would associate the /print tagged messages with the printStuff() function
	defined in this module. You can have several callback functions in a separated module if you wish
"""

def oscMessage(*msg):
	mes, x = msg
	addr = mes[0]
	numVals = len(mes[1])
	vals = mes[2:]
	map(addr, vals)
	#for i in vals:
	#	type, channel, CC = mapping(addr)
	#	value = int(vals[0] * 127)
	#	if (type != 'None'):
	#		midiMessage(type, channel, CC, value)
	
	
def map(key, vals):
	print key, vals
	tags = key.split('/')
	page = tags[1]
	if page == 'ping':
		#OSC.sendMsg("/ping", [0.5], '192.168.3.3',9000) # !! it sends by default to localhost ip "127.0.0.1" and port 9000 
		print key
	elif page == 'accxyz':
		acceration(vals)
	else:
		assert(page.isdigit())
		controlKind = tags[2].rstrip('1234567890')
		controlNum = tags[2].strip('abcdefghijklmnopqrstuvwxyz')
		for v in vals:
			if v == 'z':
				return None
		if key.find('z') != -1:
			return None
		if controlNum == '':
			controlNum = '0'
		if (len(tags) > 3):
			controlSubNums = tags[3:]
			if (len(controlSubNums) == 1):
				multiFader(page, controlKind, controlNum, controlSubNums, vals)
			elif (len(controlSubNums) == 2):
				multiToggle(page, controlKind, controlNum, controlSubNums, vals)
		else:
			if (len(vals) == 1):
				singleValuedControl(page, controlKind, controlNum, vals)
			else:
				xyControl(page, controlKind, controlNum, vals)
	   

def singleValuedControl(page, controlKind, controlNum, vals):
	assert(len(vals) == 1)
	type, channel, CC = mapTable(controlKind)
	channel = int(page)
	CC += (int(controlNum) - 1)
	value = int(vals[0] * 127)
	midiMessage(type, channel, CC, value)

def multiFader(page, controlKind, controlNum, controlSubNums, vals):
	assert(len(vals) == 1)
	assert(len(controlSubNums) == 1)
	#type, channel, CC = mapTable(controlKind)
	print "BM fader: ", controlSubNums[0], vals[0]
	bMachine.fader(int(controlSubNums[0]), vals[0]) 
	#channel = int(page)
	#CC += (int(controlNum) - 1) * 24 + (int(controlSubNums[0]) - 1)
	#value = int(vals[0] * 127)
	#midiMessage(type, channel, CC, value)
	
def multiToggle(page, controlKind, controlNum, controlSubNums, vals):
	assert(len(vals) == 1)
	assert(len(controlSubNums) == 2)
	#type, channel, CC = mapTable(controlKind)
	#channel = int(page)
	print "BM button: ", controlSubNums[0], controlSubNums[1], vals[0]
	bMachine.button(int(controlSubNums[0]), int(controlSubNums[1]), vals[0]) 
	#CC += (int(controlSubNums[0]) - 1) * 8 + (int(controlSubNums[1]) - 1)
	#value = int(vals[0] * 127)
	#midiMessage(type, channel, CC, value)

#The xyCycle feature makes it easier to use a "midi learn" function in the DAW
xyCycle = 0
def xyControl(page, controlKind, controlNum, vals):
	global xyCycle
	assert(len(vals) == 2)
	assert(controlKind == 'xy')
	type, channel, CC = mapTable(controlKind)
	channel = int(page)
	CC += (int(controlNum)-1) * 2
	if xyCycle == 0:
		value = int(vals[0] * 127)
		midiMessage(type, channel, CC, value)
		value = int(vals[1] * 127)
		midiMessage(type, channel, CC + 1, value)
		xyCycle = 1
	else:
		value = int(vals[1] * 127)
		midiMessage(type, channel, CC + 1, value)
		value = int(vals[0] * 127)
		midiMessage(type, channel, CC, value)
		xyCycle = 0
		
def mapTable(key):
	map = { \
	#(Kind, Channel, CC)
	#accell uses this CC value and the above it.
	'accel': ('CC',16,100), \
	'fader': ('CC',1,1), \
	'toggle': ('CC',1,10), \
	'push': ('NoteOn',1,40), \
	'xy': ('CC',1,20), \
	'multitoggle': ('NoteOn',1,23), \
	'multifader': ('CC',1,50), \
	'rotary': ('CC',1,30), \
	'default': (1,0) \
	}
	result = map[key]
	return result
	
"""
possible controls are:
fader
toggle
push
xy
multitoggle
rotary
multifader
led (display only)
"""
		
# def 
		
# def controlType():
	# funcMap = {
	# "a": a,
	# "A": a,
	# "b": b,
	# "exoticName": c }

	# funcString = GetFunctionAsString()
	# try:
		# f = funcMap(funcString)
	# except KeyError:
		# print "No such function"
		# raise SomethingElse
	# result = f(args)
		

		
def acceration(vals):
	for i in range(len(vals)):
		type, channel, CC = mapTable('accel')
		CC += i
		accRange = 5
		value = int(((vals[i] + (accRange/2.0)) / accRange) * 127)
		if value > 127:
			value = 127
			if debug: 
				print "ACC Over", i, vals[i], " -> ", value
		if value < 0:
			value = 0
			if debug: 
				print "ACC Undr", i, vals[i], " -> ", value
		midiMessage(type, channel, CC, value)


def oldMapTable(key):
	#TouchOSC 1.2
	map = { \
	#Simple - pg 1
	#(Channel, CC)
	'fader1': ('CC', 1,1), \
	'fader2': ('CC', 1,2), \
	'fader3': ('CC', 1,3), \
	'fader4': ('CC', 1,4), \
	'fader5': ('CC', 1,5), \
	'toggle1': ('CC', 1,6), \
	'toggle2': ('CC', 1,7), \
	'toggle3': ('CC', 1,8), \
	'toggle4': ('CC', 1,9), \
	#Simple - pg 2
	'push1': ('CC', 1,10), \
	'push2': ('CC', 1,11), \
	'push3': ('CC', 1,12), \
	'push4': ('CC', 1,13), \
	'push5': ('CC', 1,14), \
	'push6': ('CC', 1,15), \
	'push7': ('CC', 1,16), \
	'push8': ('CC', 1,17), \
	'push9': ('CC', 1,18), \
	'push10': ('CC', 1,19), \
	'push11': ('CC', 1,21), \
	'push12': ('CC', 1,22), \
	'push13': ('CC', 1,23), \
	'push14': ('CC', 1,24), \
	'push15': ('CC', 1,25), \
	'push16': ('CC', 1,26), \
	'toggle1': ('CC', 1,27), \
	'toggle2': ('CC', 1,28), \
	'toggle3': ('CC', 1,29), \
	'toggle4': ('CC', 1,30), \
	#Simple - pg 3
	'toggle1': ('CC', 1,31), \
	'toggle2': ('CC', 1,32), \
	'toggle3': ('CC', 1,33), \
	'toggle4': ('CC', 1,34), \
	'xy': (['CC', 'CC'], [1,1],[35, 36]), \
	#Simple - pg 4
	'toggle1': ('CC', 1,37), \
	'toggle2': ('CC', 1,38), \
	'toggle3': ('CC', 1,39), \
	'toggle4': ('CC', 1,40), \
	#'/4/multitoggle': (1,41), \
	'default': (1,0) \
	}
	result = ()
	try:
		result = map[key]
	except KeyError:
		if (key.find('/4/multitoggle') != -1):
			#column = key[15]
			#row = key[17]
			#import pdb; pdb.set_trace()
			result = ('CC', 1, (int(key[15])*8+int(key[17])))
		else:
			result = ('None', 0, 0)
	#print result
	#import pdb; pdb.set_trace()
	return result

def midiMessage(type, channel, d1, d2):
	typelist = {'NoteOff': 0x80, 'NoteOn': 0x90, 'PolyAft': 0xA0, \
	'CC': 0xB0, 'Program': 0xC0, 'ChAft': 0xD0, 'Wheel': 0xE0}
	assert(channel < 17)
	assert(channel > 0)
	assert(d1 > -1)
	assert(d1 < 128)
	assert(d2 > -1)
	assert(d2 < 128)
	if debug:
		print type, channel, d1, d2
	try:
		msg = [typelist[type],d1,d2]
	except KeyError:
		print "Bad MIDI message type!"
		print type, "is not a valid type!"
		assert(0)
	msg[0] += channel - 1
	#import pdb; pdb.set_trace()
	midiOutput.Write([[msg,pypm.Time()]])

	
"""
Message					Status		Data 1			Data 2
Note Off				8n			Note Number		Velocity
Note On					9n			Note Number		Velocity
Polyphonic Aftertouch	An			Note Number		Pressure
Control Change			Bn			Controller Number	Data
Program Change			Cn			Program Number	Unused
Channel Aftertouch		Dn			Pressure		Unused
Pitch Wheel				En			LSB 			MSB
"""
if __name__ == '__main__': myTest()